<?php

namespace api\libs\swoft\core;

use Yii;
use yii\base\Exception;

/**
 * SWOFT(SWOFT Service) PHP SDK v0.9.0
 */
//设置默认时区
date_default_timezone_set('Asia/Shanghai');

if (!defined('SWOFT_API_PATH'))
    define('SWOFT_API_PATH', dirname(__FILE__));

define('SWOFT_DISPLAY_LOG', false); //浏览器上显示log
define('SWOFT_LOG', true);

//加载RequestCore
require_once SWOFT_API_PATH . DIRECTORY_SEPARATOR . 'requestcore.class.php';

//加载MimeTypes
require_once SWOFT_API_PATH . DIRECTORY_SEPARATOR . 'mimetypes.class.php';

/**
 * 异常类，继承自基类
 */
class SWOFT_Exception extends Exception
{

}

//检测get_loaded_extensions函数是否被禁用。
if (function_exists('get_loaded_extensions')) {
    //检测curl扩展
    $extensions = get_loaded_extensions();
    if ($extensions) {
        if (!in_array('curl', $extensions)) {
            throw new SWOFT_Exception(SWOFT_CURL_EXTENSION_MUST_BE_LOAD);
        }
    } else {
        throw new SWOFT_Exception(SWOFT_NO_ANY_EXTENSIONS_LOADED);
    }
} else {
    throw new SWOFT_Exception('Function get_loaded_extensions has been disabled,Pls check php config.');
}

/**
 * SwoftCore 基础类
 * @author Johnson
 */
class SwoftCore
{

    // 内部常量
    const SWOFT_RESOURCE = 'bucket';
    const SWOFT_HEADERS = 'headers';
    const SWOFT_METHOD = 'method';
    const SWOFT_QUERY = 'query';
    const SWOFT_BASENAME = 'basename';
    const SWOFT_CONTENT_MD5 = 'Content-Md5';
    const SWOFT_CONTENT_TYPE = 'Content-Type';
    const SWOFT_CONTENT_LENGTH = 'Content-Length';
    const SWOFT_IF_MODIFIED_SINCE = 'If-Modified-Since';
    const SWOFT_IF_UNMODIFIED_SINCE = 'If-Unmodified-Since';
    const SWOFT_IF_MATCH = 'If-Match';
    const SWOFT_IF_NONE_MATCH = 'If-None-Match';
    const SWOFT_CACHE_CONTROL = 'Cache-Control';
    const SWOFT_EXPIRES = 'Expires';
    const SWOFT_PREAUTH = 'preauth';
    const SWOFT_CONTENT = 'content';
    const SWOFT_BODY = 'body';
    const SWOFT_HOST = 'Host';
    const SWOFT_DATE = 'Date';
    const SWOFT_FILE = 'file';
    const SWOFT_DSP_ID = 'appid';
    const SWOFT_DSP_TOKEN = 'token';

    /**
     * 查询字符串，对应数据为一个数组
     */
    const SWOFT_QUERY_STRING = 'query_string';
    const SWOFT_DEFAULT_PREFIX = 'x-cdb-';
    //私有URL变量
    const SWOFT_URL_ACCESS_KEY_ID = 'SWOFTAccessKeyId';
    const SWOFT_URL_EXPIRES = 'Expires';
    const SWOFT_URL_SIGNATURE = 'Signature';
    //HTTP方法
    const SWOFT_HTTP_GET = 'GET';
    const SWOFT_HTTP_PUT = 'PUT';
    const SWOFT_HTTP_HEAD = 'HEAD';
    const SWOFT_HTTP_POST = 'POST';
    const SWOFT_HTTP_DELETE = 'DELETE';
    const SWOFT_HTTP_PATCH = 'PATCH';

    /**
     * 是否使用SSL
     */
    protected $useSSL = false;

    /**
     * 是否开启debug模式
     */
    private $debugMode = true;

    /**
     * 最大重试次数
     */
    private $max_retries = 1;

    /**
     * 已经重试次数
     */
    private $redirects = 0;

    /**
     * 请求URL
     */
    private $_requestUrl;

    /**
     * SWOFT API ACCESS ID
     */
    private $accessKey;

    /**
     * SWOFT API ACCESS KEY
     */
    private $accessSecret;

    /**
     * hostname
     */
    private $hostname;

    /**
     * port number
     */
    private $port;
    private $__helper;

    /* %******************************************************************************************************% */

    /**
     * 默认构造函数
     *
     * @param string $_accessKey (Optional)
     * @param string $accessSecret (Optional)
     * @param string $hostname (Optional)
     *
     * @throws SWOFT_Exception
     */
    public function __construct($accessKey = null, $accessSecret = null, $hostname = null)
    {
        if ($accessKey && $accessSecret) {
            $this->accessKey = $accessKey;
            $this->accessSecret = $accessSecret;
        } else {
            throw new SWOFT_Exception("密钥必填");
        }

        //校验hostname
        if (null !== $hostname) {
            $this->hostname = $hostname;
        }

        $this->__helper = new SwoftHelper();
    }

    /**
     * 设置debug模式
     *
     * @param boolean $debug (Optional)
     *
     * @return void
     */
    public function setDebug($debug = true)
    {
        $this->debugMode = $debug;
    }

    /**
     * 设置最大尝试次数
     *
     * @param int $max_retries
     *
     * @return void
     */
    public function set_max_retries($max_retries = 3)
    {
        $this->max_retries = $max_retries;
    }

    /**
     * 获取最大尝试次数
     * @return int
     */
    public function get_max_retries()
    {
        return $this->max_retries;
    }

    /**
     * 设置主机名称
     *
     * @param string $hostname
     * @param int $port
     *
     * @return void
     */
    public function setHostname($hostname, $port = null)
    {
        $this->hostname = $hostname;

        if ($port) {
            $this->port = $port;
            $this->hostname .= ':' . $port;
        }
        return $this;
    }

    /**
     * Authorization
     *
     * @param array $options (Required)
     *
     * @throws SWOFT_Exception
     * @since 2012-11-15
     */
    public function auth($options)
    {
        //开始记录LOG
        $msg = "---LOG START--------------------------------------------------\n";

        //定义scheme
        $scheme = $this->useSSL ? 'https://' : 'http://';
        $hostname = (isset($options[self::SWOFT_RESOURCE]) && '' !== $options[self::SWOFT_RESOURCE]) ? $this->hostname . '/' . $options[self::SWOFT_RESOURCE] : $this->hostname;

        $stringToSign = '';
//        $request_headers = Yii::$app->request->headers;
        $headers = [
            self::SWOFT_CONTENT_MD5 => '',
            self::SWOFT_CONTENT_TYPE => isset($options[self::SWOFT_CONTENT_TYPE]) ? $options[self::SWOFT_CONTENT_TYPE] : 'application/x-www-form-urlencoded',
            self::SWOFT_DATE => isset($options[self::SWOFT_DATE]) ? $options[self::SWOFT_DATE] : gmdate('D, d M Y H:i:s \G\M\T'),
            self::SWOFT_HOST => $this->hostname,
//            self::SWOFT_DSP_ID => $request_headers->get('appid'),
//            self::SWOFT_DSP_TOKEN => $request_headers->get('token'),
        ];

        $query_string_params = $options[self::SWOFT_QUERY_STRING];
        $queryString = http_build_query($query_string_params);

        //合并 HTTP headers
        if (isset($options [self::SWOFT_HEADERS])) {
            $headers = array_merge($headers, $options[self::SWOFT_HEADERS]);
        }

        //生成请求URL
        $conjunction = '?';
        $nonSignableResource = '';

        if ($queryString !== '') {
            $nonSignableResource .= $conjunction . $queryString;
            $conjunction = '&';
        }

        $this->_requestUrl = $scheme . $hostname . $nonSignableResource;

        $msg .= "---REQUEST URL:---\n" . $this->_requestUrl . "\n";

        //创建请求
        $request = new RequestCore($this->_requestUrl);

        if (isset($options[self::SWOFT_METHOD])) {
            $request->set_method($options[self::SWOFT_METHOD]);
            $stringToSign .= $options[self::SWOFT_METHOD] . "\n";
        }

        if (isset($options[self::SWOFT_CONTENT])) {
            $request->set_body($options[self::SWOFT_CONTENT]);
//            if ($headers[self::SWOFT_CONTENT_TYPE] === 'application/x-www-form-urlencoded') {
//                $headers[self::SWOFT_CONTENT_TYPE] = 'application/octet-stream';
//            }

            $headers[self::SWOFT_CONTENT_LENGTH] = strlen($options [self::SWOFT_CONTENT]);
            $headers[self::SWOFT_CONTENT_MD5] = $this->__helper->hex2Base64(md5($options[self::SWOFT_CONTENT]));
        }
        if (isset($options[self::SWOFT_FILE])) {
            $request->set_body($options[self::SWOFT_FILE]);
        }
        uksort($headers, 'strnatcasecmp');
        foreach ($headers as $header_key => $header_value) {
            $header_value = str_replace(["\r", "\n"], '', $header_value);
            if ($header_value !== '') {
                $request->add_header($header_key, $header_value);
            }
            if (
                strtolower($header_key) === 'content-md5' ||
                strtolower($header_key) === 'content-type' ||
                strtolower($header_key) === 'date' ||
                (isset($options['self::SWOFT_PREAUTH']) && (integer)$options['self::SWOFT_PREAUTH'] > 0)
            ) {
                $stringToSign .= $header_value . "\n";
            } elseif (substr(strtolower($header_key), 0, 6) === self::SWOFT_DEFAULT_PREFIX) {
                $stringToSign .= strtolower($header_key) . ':' . $header_value . "\n";
            }
        }

        $stringToSign .= '/' . $options[self::SWOFT_RESOURCE];
        $msg .= "STRING TO SIGN:----\n" . $stringToSign . "\n";
        $signature = base64_encode(hash_hmac('sha1', $stringToSign, $this->accessSecret, true));
        $request->add_header('Authorization', 'SWOFT:' . $signature);
        if (isset($options[self::SWOFT_PREAUTH]) && (integer)$options[self::SWOFT_PREAUTH] > 0) {
            return $this->_requestUrl . $conjunction . self::SWOFT_URL_ACCESS_KEY_ID . '=' . $this->accessKey
                . '&' . self::SWOFT_URL_EXPIRES . '=' . $options[self::SWOFT_PREAUTH] . '&' . self::SWOFT_URL_SIGNATURE . '=' . rawurlencode($signature);
        } elseif (isset($options[self::SWOFT_PREAUTH])) {
            return $this->_requestUrl;
        }

        if ($this->debugMode) {
            $request->debug_mode = $this->debugMode;
        }
        $msg .= "REQUEST HEADERS:----\n" . serialize($request->request_headers) . "\n";
        $request->send_request();
        $response_header = $request->get_response_header();
        $response_header['x-cdb-request-url'] = $this->_requestUrl;
        $response_header['x-cdb-redirects'] = $this->redirects;
        $response_header['x-cdb-stringtosign'] = $stringToSign;
        $response_header['x-cdb-requestheaders'] = $request->request_headers;
        $msg .= "RESPONSE HEADERS:----\n" . serialize($response_header) . "\n";

        $data = new ResponseCore($response_header, $request->get_response_body(), $request->get_response_code());
        if ((integer)$request->get_response_code() === 400 /* Bad Request */ || (integer)$request->get_response_code() === 500 /* Internal Error */ || (integer)$request->get_response_code() === 503 /* Service Unavailable */) {
            if ($this->redirects <= $this->max_retries) {
                //设置休眠
                $delay = (integer)(pow(4, $this->redirects) * 100000);
                usleep($delay);
                $this->redirects++;
                $data = $this->auth($options);
            }
        }

        $msg .= "RESPONSE DATA:----\n" . serialize($data) . "\n";
        $msg .= date('Y-m-d H:i:s') . ":---LOG END---------------------------------------------------------------------------\n";
        //add log
        $this->log($msg, $headers);
        $this->redirects = 0;
        return $data;
    }

    /* %******************************************************************************************************% */
    //带签名的url相关

    /**
     * 获取带签名的url
     *
     * @param string $bucket (Required)
     * @param string $object (Required)
     * @param int $timeout (Optional)
     * @param array $options (Optional)
     *
     * @return string
     */
    public function getSignUrl($bucket, $timeout = 60, $options = null)
    {
        if (!$options) {
            $options = [];
        }

        $options[self::SWOFT_RESOURCE] = $bucket;
        $options[self::SWOFT_METHOD] = self::SWOFT_HTTP_GET;
        $options[self::SWOFT_CONTENT_TYPE] = '';

        $timeout = time() + $timeout;
        $options[self::SWOFT_PREAUTH] = $timeout;
        $options[self::SWOFT_DATE] = $timeout;

        return $this->auth($options);
    }

    /**
     * 记录日志
     *
     * @param string $msg (Required)
     *
     * @throws SWOFT_Exception
     * @since 2012-11-15
     * @return void
     */
    private function log($msg, $headers)
    {
        $account_id = isset($headers[self::SWOFT_DSP_ID]) ? $headers[self::SWOFT_DSP_ID] : 0;
        $log_path = dirname(__FILE__) . DIRECTORY_SEPARATOR . 'logs' . DIRECTORY_SEPARATOR . $account_id . DIRECTORY_SEPARATOR;

        //检测日志目录是否存在
        if (!file_exists($log_path)) {
            mkdir($log_path, 0777, true);
            chmod($log_path, 0777);
        }
        $log_name = $log_path . 'SWOFT' . date('Y-m-d H') . '.log';
        if (SWOFT_DISPLAY_LOG) {
            echo $msg . "\n<br/>";
        }

        if (SWOFT_LOG) {
            if (!error_log(date('Y-m-d H:i:s') . " : " . $msg . "\n", 3, $log_name)) {
                throw new SWOFT_Exception(SWOFT_WRITE_LOG_TO_FILE_FAILED);
            }
        }
    }

}

/**
 * 帮助类
 * @author Johnson
 */
class SwoftHelper
{

    /**
     * 生成query params
     *
     * @param array $array 关联数组
     *
     * @return string 返回诸如 key1=value1&key2=value2
     */
    public function to_query_string($param = [])
    {
        $temp = [];

        foreach ($param as $key => $value) {
            if (is_string($key) && !is_array($value)) {
                $temp[] = rawurlencode($key) . '=' . rawurlencode($value);
            }
        }

        return implode('&', $temp);
    }

    /**
     * 转化十六进制的数据为base64
     *
     * @param string $str (Required) 要转化的字符串
     *
     * @return string Base64-encoded string.
     */
    public function hex2Base64($str)
    {
        $result = '';

        for ($i = 0; $i < strlen($str); $i += 2) {
            $result .= chr(hexdec(substr($str, $i, 2)));
        }

        return base64_encode($result);
    }

    /**
     * 检测是否GB2312编码
     *
     * @param string $str
     *
     * @return boolean false UTF-8编码  TRUE GB2312编码
     */
    public function isGb2312($str)
    {
        for ($i = 0; $i < strlen($str); $i++) {
            $v = ord($str[$i]);
            if ($v > 127) {
                if (($v >= 228) && ($v <= 233)) {
                    if (($i + 2) >= (strlen($str) - 1))
                        return true;  // not enough characters
                    $v1 = ord($str[$i + 1]);
                    $v2 = ord($str[$i + 2]);
                    if (($v1 >= 128) && ($v1 <= 191) && ($v2 >= 128) && ($v2 <= 191))
                        return false;   //UTF-8编码
                    else
                        return true;    //GB编码
                }
            }
        }
    }

    /**
     * 检测是否GBK编码
     *
     * @param string $str
     * @param boolean $gbk
     *
     * @return boolean
     */
    public function check_char($str, $gbk = true)
    {
        for ($i = 0; $i < strlen($str); $i++) {
            $v = ord($str[$i]);
            if ($v > 127) {
                if (($v >= 228) && ($v <= 233)) {
                    if (($i + 2) >= (strlen($str) - 1))
                        return $gbk ? true : false;  // not enough characters
                    $v1 = ord($str[$i + 1]);
                    $v2 = ord($str[$i + 2]);
                    if ($gbk) {
                        return (($v1 >= 128) && ($v1 <= 191) && ($v2 >= 128) && ($v2 <= 191)) ? false : true; //GBK
                    } else {
                        return (($v1 >= 128) && ($v1 <= 191) && ($v2 >= 128) && ($v2 <= 191)) ? true : false;
                    }
                }
            }
        }
        return $gbk ? true : false;
    }

    /**
     * 校验BUCKET/OBJECT/OBJECT GROUP是否为空
     *
     * @param  string $name (Required)
     * @param  string $errMsg (Required)
     *
     * @throws SWOFT_Exception
     * @since 2011-12-27
     * @return void
     */
    private function is_empty($name, $errMsg)
    {
        if (empty($name)) {
            throw new SWOFT_Exception($errMsg);
        }
    }

}
